﻿using Google.Protobuf;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

public enum EMessageType
{
    RUDP_NONE,
    RUDP_HELLO,
    RUDP_PING,
    RUDP_ACK,
    RUDP_C2S_PLAYER_JOIN,
    RUDP_S2C_PLAYER_JOIN,
    RUDP_C2S_PLAYER_SYNC,
    RUDP_S2C_PLAYER_SYNC,
}

class MessageManager
{
    public static MessageManager Instance = new MessageManager();

    public Dictionary<EMessageType, Type> MessageDict = new Dictionary<EMessageType, Type>
    {
        { EMessageType.RUDP_HELLO, typeof(Hello)},
        { EMessageType.RUDP_PING, typeof(Ping)},
        { EMessageType.RUDP_ACK, typeof(Ack)},
        { EMessageType.RUDP_C2S_PLAYER_JOIN, typeof(C2S_PlayerJoin)},
        { EMessageType.RUDP_S2C_PLAYER_JOIN, typeof(S2C_PlayerJoin)},
        { EMessageType.RUDP_C2S_PLAYER_SYNC, typeof(C2S_PlayerSync)},
        { EMessageType.RUDP_S2C_PLAYER_SYNC, typeof(S2C_PlayerSync)},
    };

    public EMessageType GetMessageType(System.Type type)
    {
        if (MessageDict.ContainsValue(type))
        {
            foreach (var item in MessageDict)
            {
                if (item.Value == type)
                {
                    return item.Key;
                }
            }
        }
        LogManager.Error("cannot find message type for " + type);
        return EMessageType.RUDP_NONE;
    }

    public EMessageType GetMessageType<T>(T msg)
        where T : IMessage
    {
        if (msg == null)
        {
            return EMessageType.RUDP_NONE;
        }
        return GetMessageType(msg.GetType());
    }

    public byte[] GetMessageTypeByteArray<T>(T msg)
        where T : IMessage
    {
        return BitConverter.GetBytes((ushort)MessageManager.Instance.GetMessageType(msg));
    }

    public byte[] GetMessageTypeByteArray(System.Type type)
    {
        return BitConverter.GetBytes((ushort)MessageManager.Instance.GetMessageType(type));
    }

    public bool IsReliable(IMessage msg)
    {
        if (msg == null)
        {
            return false;
        }
        var t = GetMessageType(msg);
        return t != EMessageType.RUDP_ACK && t != EMessageType.RUDP_HELLO && t != EMessageType.RUDP_NONE && t != EMessageType.RUDP_PING
                && t != EMessageType.RUDP_C2S_PLAYER_SYNC
                && t != EMessageType.RUDP_S2C_PLAYER_SYNC
                ;
    }

    public void SetSeqID(IMessage msg, uint seqId)
    {
        var v = new BaseMessage();
        msg.GetType().GetProperty("Msg").SetValue(msg, v, null);
        v.SeqId = seqId;
    }

    public uint GetSeqID(IMessage msg)
    {
        var v = msg.GetType().GetProperty("Msg").GetValue(msg, null);
        if (v == null)
        {
            return 0;
        }

        return (v as BaseMessage).SeqId;
    }

    public uint GetAckBits(uint curSeqId, List<IMessage> msgs)
    {
        if (msgs == null || curSeqId == 0)
        {
            return 0;
        }

        uint bits = 0;
        uint seqId = 0;
        foreach (var item in msgs)
        {
            seqId = GetSeqID(item);
            if (seqId != 0 && seqId < curSeqId)
            {
                bits |= (uint)(1 << (int)(curSeqId - seqId - (uint)1));
            }
        }
        return bits;
    }

#if !SERVER
    UdpClient m_Client = new UdpClient();
    MemoryStream m_SendStream = new MemoryStream(new byte[1024]);
    MemoryStream m_ReceiveStream;
    byte[] m_RecvBuffer = new byte[1024];
    byte[] m_RecvProcessBuffer = new byte[1024];
    byte[] m_MessageTypeBuffer = new byte[2];
    Thread m_ReceiveThread;
    Thread m_SendThread;
    Queue<IMessage> m_PendingMessages = new Queue<IMessage>();
    byte[] m_RecvBytes = new byte[1024];
    static UniqueIDGenerator m_SeqIDGenerator = new UniqueIDGenerator();
    List<IMessage> m_RecvedMessages = new List<IMessage>(); // max num: 32
    Queue<IMessage> m_RecvWaitingProcessMessages = new Queue<IMessage>();
    Queue<IMessage> m_RecvWaitingProcessReliableMessages = new Queue<IMessage>();
    List<IMessage> m_RecvUnorderMessages = new List<IMessage>();
    Thread m_KeepAliveThread;
    IMessage m_PingMessage = new Ping();

    public IPEndPoint GetLocalIP()
    {
        return m_Client.Client.LocalEndPoint as IPEndPoint;
    }

    public void Start()
    {
        m_Client.Connect("127.0.0.1", 1234);
        m_ReceiveStream = new MemoryStream(m_RecvBuffer);
        m_SendThread = new Thread(SendThread);
        m_SendThread.Start();
        m_ReceiveThread = new Thread(RecvThread);
        m_ReceiveThread.Start();
        m_KeepAliveThread = new Thread(KeepAliveThread);
        m_KeepAliveThread.Start();
    }

    public void Stop()
    {
        if (m_SendStream != null)
        {
            m_SendStream.Close();
        }
        if (m_ReceiveStream != null)
        {
            m_ReceiveStream.Close();
        }
        if (m_SendThread != null)
        {
            m_SendThread.Abort();
        }
        if (m_ReceiveThread != null)
        {
            m_ReceiveThread.Abort();
        }
        if (m_KeepAliveThread != null)
        {
            m_KeepAliveThread.Abort();
        }
        m_Client.Close();
    }

    void SendThread()
    {
        while (true)
        {
            //LogManager.Log("send thread count=" + m_PendingMessages.Count);
            while (m_PendingMessages.Count > 0)
            {
                var m = m_PendingMessages.Dequeue();
                SendMessage(m);
            }
            Thread.Sleep(33);
        }
    }

    void RecvThread()
    {
        while (true)
        {
            try
            {
                if (m_Client.Available > 0)
                {
                    IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
                    ReceiveMessage(m_Client.Receive(ref ep));
                }
            }
            catch (Exception ex) { LogManager.Log(ex); }

            Thread.Sleep(33);
        }
    }

    void KeepAliveThread()
    {
        while (true)
        {
            Send(m_PingMessage);
            Thread.Sleep(33);
        }
    }

    public void Send<T>(T msg)
        where T : IMessage
    {
        if (msg == null)
        {
            return;
        }
        m_PendingMessages.Enqueue(msg);
    }

    public void Recv()
    {
        if (m_RecvWaitingProcessMessages.Count > 0)
        {
            var msg = m_RecvWaitingProcessMessages.Dequeue();
            MessageDispatcher.Instance.Invoke(GetMessageType(msg), msg);
        }
    }

    void SendMessage<T>(T msg)
        where T : IMessage
    {
        if (msg.GetType() == typeof(C2S_PlayerSync))
        {
            var m = (msg as C2S_PlayerSync);
            var s = m.PosX + ", " + m.PosY + ", " + m.PosZ;
            LogManager.Log(msg + ";" + s, true);
        }
        else
        {
            LogManager.Log(msg, true);
        }

        // resend or not.
        uint seqId = GetSeqID(msg);
        uint newId = 0;
        if (IsReliable(msg))
        {
            if (seqId == 0)
            {
                newId = m_SeqIDGenerator.GetUniqueID();
                SetSeqID(msg, newId);
            }
            PendingAckManager.Instance.AddInfo(0, seqId == 0 ? newId : seqId, msg);
        }

        m_SendStream.Position = 0;
        m_SendStream.Write(MessageManager.Instance.GetMessageTypeByteArray(msg), 0, 2);
        var bs = msg.ToByteArray();
        m_SendStream.Write(bs, 0, bs.Length);
        m_Client.Send(m_SendStream.ToArray(), (int)m_SendStream.Position);
    }

    IMessage ReceiveMessage(byte[] bs)
    {
        if (bs == null || bs.Length < 2)
        {
            return null;
        }

        int leftBytes = bs.Length;
        m_ReceiveStream.Position = 0;
        m_ReceiveStream.Write(bs, 0, bs.Length);
        m_ReceiveStream.Position = 0;
        m_ReceiveStream.Read(m_MessageTypeBuffer, 0, 2);
        leftBytes -= 2;
        var type = (EMessageType)BitConverter.ToUInt16(m_MessageTypeBuffer, 0);
        if (MessageDict.ContainsKey(type))
        {
            var msg = Activator.CreateInstance(MessageDict[type]) as IMessage;
            if (leftBytes > 0)
            {
                Array.Copy(m_RecvBuffer, (int)m_ReceiveStream.Position, m_RecvProcessBuffer, 0, leftBytes);
                msg.MergeFrom(m_RecvProcessBuffer, 0, leftBytes);
            }
            LogManager.Log(msg, true);
            var curSeqId = GetSeqID(msg);
            if (IsReliable(msg))
            {
                m_RecvedMessages.Add(msg);
                if (m_RecvedMessages.Count > 32)
                {
                    m_RecvedMessages.RemoveAt(0);
                }


                // reorder
                m_RecvUnorderMessages.Add(msg);
                // TODO number cannot be too large, otherwise there will be a problem
                m_RecvUnorderMessages.Sort((x, y) => (int)x.GetSeqID() - (int)y.GetSeqID());
                uint newId = 0;
                if (m_RecvWaitingProcessReliableMessages.Count > 0)
                {
                    curSeqId = GetSeqID(m_RecvWaitingProcessReliableMessages.Peek());
                }
                else
                {
                    curSeqId = 0;
                }
                if (m_RecvUnorderMessages.Count > 0)
                {
                    newId = m_RecvUnorderMessages[0].GetSeqID();
                }
                else
                {
                    newId = 0;
                }
                while (m_RecvUnorderMessages.Count > 0 && (newId == curSeqId + 1
                    || curSeqId == 0))
                {
                    msg = m_RecvUnorderMessages[0];
                    m_RecvUnorderMessages.RemoveAt(0);
                    m_RecvWaitingProcessReliableMessages.Enqueue(msg);
                    if (m_RecvWaitingProcessReliableMessages.Count > 0)
                    {
                        curSeqId = GetSeqID(m_RecvWaitingProcessReliableMessages.Peek());
                    }
                    else
                    {
                        curSeqId = 0;
                    }
                    if (m_RecvUnorderMessages.Count > 0)
                    {
                        newId = m_RecvUnorderMessages[0].GetSeqID();
                    }
                    else
                    {
                        newId = 0;
                    }
                }


                Send(new Ack { SeqId = curSeqId, AckBits = GetAckBits(curSeqId, m_RecvedMessages) });
            }
            else
            {
                m_RecvWaitingProcessMessages.Enqueue(msg);
            }

            foreach (var item in m_RecvWaitingProcessReliableMessages)
            {
                m_RecvWaitingProcessMessages.Enqueue(item);
            }
            m_RecvWaitingProcessReliableMessages.Clear();

            //var s = string.Empty;
            //foreach (var item in m_RecvWaitingProcessMessages)
            //{
            //    s += item.GetSeqID() + ", ";
            //}
            //LogManager.Error("m_RecvWaitingProcessMessages seq ids=" + s);
            return msg;
        }


        return null;
    }
#endif
}
